#include "file.h"

#include <cerrno>
#include <unistd.h>
#include <sys/uio.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <stdexcept>
#include <cassert>

#include "exception.h"


namespace evm {

// Wrap call to f(args) in loop to retry on EINTR
template<class F, class... Args>
ssize_t wrapNoInt(F f, Args... args) {
    ssize_t r;
    do {
        r = f(args...);
    } while (r == -1 && errno == EINTR);
    return r;
}

inline void incr(ssize_t) { }
inline void incr(ssize_t n, off_t& offset) { offset += n; }

// Wrap call to read/pread/write/pwrite(fd, buf, count, offset?) to retry on
// incomplete reads / writes.  The variadic argument magic is there to support
// an additional argument (offset) for pread / pwrite; see the incr() functions
// above which do nothing if the offset is not present and increment it if it
// is.
template <class F, class... Offset>
ssize_t wrapFull(F f, int fd, void* buf, size_t count, Offset... offset) {
    char* b = static_cast<char*>(buf);
    ssize_t totalBytes = 0;
    ssize_t r;
    do {
        r = f(fd, b, count, offset...);
        if (r == -1) {
            if (errno == EINTR) {
                continue;
            }
            return r;
        }

        totalBytes += r;
        b += r;
        count -= r;
        incr(r, offset...);
    } while (r != 0 && count);  // 0 means EOF

    return totalBytes;
}

// Wrap call to readv/preadv/writev/pwritev(fd, iov, count, offset?) to
// retry on incomplete reads / writes.
template <class F, class... Offset>
ssize_t wrapvFull(F f, int fd, iovec* iov, int count, Offset... offset) {
    ssize_t totalBytes = 0;
    size_t r;
    do {
        r = f(fd, iov, count, offset...);
        if (r == (size_t)-1) {
            if (errno == EINTR) {
                continue;
            }
            return r;
        }

        if (r == 0) {
            break;  // EOF
        }

        totalBytes += r;
        incr(r, offset...);
        while (r != 0 && count != 0) {
            if (r >= iov->iov_len) {
                r -= iov->iov_len;
                ++iov;
                --count;
            } else {
                iov->iov_base = static_cast<char*>(iov->iov_base) + r;
                iov->iov_len -= r;
                r = 0;
            }
        }
    } while (count);

    return totalBytes;
}

int openNoInt(const char* name, int flags, mode_t mode) {
    return wrapNoInt(open, name, flags, mode);
}

int closeNoInt(int fd) {
    int r = close(fd);
    // Ignore EINTR.  On Linux, close() may only return EINTR after the file
    // descriptor has been closed, so you must not retry close() on EINTR --
    // in the best case, you'll get EBADF, and in the worst case, you'll end up
    // closing a different file (one opened from another thread).
    //
    // Interestingly enough, the Single Unix Specification says that the state
    // of the file descriptor is unspecified if close returns EINTR.  In that
    // case, the safe thing to do is also not to retry close() -- leaking a file
    // descriptor is definitely better than closing the wrong file.
    if (r == -1 && errno == EINTR) {
        r = 0;
    }
    return r;
}

int fsyncNoInt(int fd) {
    return wrapNoInt(fsync, fd);
}

int dupNoInt(int fd) {
    return wrapNoInt(dup, fd);
}

int dup2NoInt(int oldfd, int newfd) {
    return wrapNoInt(dup2, oldfd, newfd);
}

int fdatasyncNoInt(int fd) {
    return wrapNoInt(fdatasync, fd);
}

int ftruncateNoInt(int fd, off_t len) {
    return wrapNoInt(ftruncate, fd, len);
}

int truncateNoInt(const char* path, off_t len) {
    return wrapNoInt(truncate, path, len);
}

int flockNoInt(int fd, int operation) {
    return wrapNoInt(flock, fd, operation);
}

int shutdownNoInt(int fd, int how) {
    return wrapNoInt(shutdown, fd, how);
}

ssize_t readNoInt(int fd, void* buf, size_t count) {
    return wrapNoInt(read, fd, buf, count);
}

ssize_t preadNoInt(int fd, void* buf, size_t count, off_t offset) {
    return wrapNoInt(pread, fd, buf, count, offset);
}

ssize_t readvNoInt(int fd, const iovec* iov, int count) {
    return wrapNoInt(writev, fd, iov, count);
}

ssize_t writeNoInt(int fd, const void* buf, size_t count) {
    return wrapNoInt(write, fd, buf, count);
}

ssize_t pwriteNoInt(int fd, const void* buf, size_t count, off_t offset) {
    return wrapNoInt(pwrite, fd, buf, count, offset);
}

ssize_t writevNoInt(int fd, const iovec* iov, int count) {
    return wrapNoInt(writev, fd, iov, count);
}

ssize_t readFull(int fd, void* buf, size_t count) {
    return wrapFull(read, fd, buf, count);
}

ssize_t preadFull(int fd, void* buf, size_t count, off_t offset) {
    return wrapFull(pread, fd, buf, count, offset);
}

ssize_t writeFull(int fd, const void* buf, size_t count) {
    return wrapFull(write, fd, const_cast<void*>(buf), count);
}

ssize_t pwriteFull(int fd, const void* buf, size_t count, off_t offset) {
    return wrapFull(pwrite, fd, const_cast<void*>(buf), count, offset);
}

ssize_t readvFull(int fd, iovec* iov, int count) {
    return wrapvFull(readv, fd, iov, count);
}

ssize_t writevFull(int fd, iovec* iov, int count) {
    return wrapvFull(writev, fd, iov, count);
}

File::File(int fd, bool owns_fd) : fd_(fd), owns_fd_(owns_fd) {
    assert(fd >= -1);  // fd must be -1 or non-negative
    assert(fd != -1 || !owns_fd);  // cannot own -1
}

File::File(const std::string& name, int flags, mode_t mode)
        : name_(name), fd_(openNoInt(name.data(), flags, mode)), owns_fd_(true) {
    if (fd_ == -1)
        throw std::system_error(errno, std::system_category(),
                                "open(" + name + ", " + std::to_string(flags)
                                + ", " + std::to_string(mode) + ") failed");
}

File::File(const char *name, int flags, mode_t mode)
        : File(std::string(name), flags, mode) {
}

File::~File() {
    closeNoThrow();
}

File::File(File&& other) noexcept
    : fd_(other.fd_), owns_fd_(other.owns_fd_) {
    other.release();
}

File& File::operator=(File&& other) {
    closeNoThrow();
    swap(other);
    return *this;
}

void File::close() {
    if (!closeNoThrow()) {
        throw_system_error("close() failed");
    }
}

bool File::closeNoThrow() {
    int r = owns_fd_ ? ::close(fd_) : 0;
    release();
    return r == 0;
}

int File::release() noexcept {
    int released = fd_;
    fd_ = -1;
    owns_fd_ = false;
    return released;
}

void File::swap(File& other) {
    using std::swap;
    swap(fd_, other.fd_);
    swap(owns_fd_, other.owns_fd_);
}

bool File::read(void* buf, size_t& count) {
    auto read_count = readFull(fd_, buf, count);
    if (read_count < 0) {  // error
        count = 0;
        return false;
    } else {
        bool res = (static_cast<size_t>(read_count) == count);
        count = static_cast<size_t>(read_count);
        return res;
    }
}

bool File::read(void* buf, const size_t& count) {
    auto c = count;
    return read(buf, static_cast<size_t&>(c));
}

bool File::write(const void* buf, size_t& count) {
    auto write_count = writeFull(fd_, buf, count);
    if (write_count < 0) {  // error
        count = 0;
        return false;
    } else {
        bool res = (static_cast<size_t>(write_count) == count);
        count = static_cast<size_t>(write_count);
        return res;
    }
}

bool File::write(const void* buf, const size_t& count) {
    auto c = count;
    return write(buf, static_cast<size_t&>(c));
}

size_t File::size() {
    struct stat s;
    if (stat(name_.data(), &s) == -1)
        throw std::runtime_error("stat file failed");
    return static_cast<size_t>(s.st_size);
}

std::string File::read_all(const char *name) {
    File f(name);
    auto size = f.size();
    char bytes[size + 1];
    f.read(bytes, size);
    bytes[size] = '\0';
    return std::string(bytes);
}

std::string File::read_all(const std::string& name) {
    return read_all(name.data());
}

void File::write_all(const char* name, const std::string& contents) {
    File f(name, O_CREAT | O_WRONLY | O_TRUNC);
    f.write(contents.c_str(), contents.size());
}

void File::write_all(const std::string& name, const std::string& contents) {
    write_all(name.c_str(), contents);
}

}
